package anni.core.dao.hibernate;

import java.io.Serializable;

import java.lang.reflect.InvocationTargetException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import anni.core.dao.support.Page;

import anni.core.utils.BeanUtils;

import org.apache.commons.beanutils.PropertyUtils;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;

import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import org.hibernate.impl.CriteriaImpl;

import org.hibernate.metadata.ClassMetadata;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;


/**
 * Hibernate Dao的泛型基类.
 * <p/>
 * 继承于Spring的<code>HibernateDaoSupport</code>,提供分页函数和若干便捷查询方法，并对返回值作了泛型类型转换.
 *
 * @author calvin
 * @author tin
 * @see HibernateDaoSupport
 * @see HibernateEntityDao
 */
@SuppressWarnings("unchecked")
public class HibernateGenericDao extends HibernateDaoSupport {
    /**
     * 根据ID获取对象.
     * 实际调用Hibernate的session.get()方法返回实体或其proxy对象.
     * 如果对象不存在，返回null.
     */
    public <T> T get(Class<T> entityClass, Serializable id) {
        return (T) getHibernateTemplate().get(entityClass, id);
    }

    /**
     * 根据ID获取对象.
     * 实际调用Hibernate的session.load()方法返回实体或其proxy对象.
     * 网上有的地方说，对象不存在就抛出ObjectNotFindException，但实际使用的时候总是返回proxy。
     * 而在是具体使用的时候会出问题，根本没办法判断这个id是否存在，真是没办法。
     */
    public <T> T load(Class<T> entityClass, Serializable id) {
        return (T) getHibernateTemplate().load(entityClass, id);
    }

    /**
     * 获取全部对象.
     */
    public <T> List<T> getAll(Class<T> entityClass) {
        return getHibernateTemplate().loadAll(entityClass);
    }

    /**
     * 获取全部对象,带排序字段与升降序参数.
     */
    public <T> List<T> getAll(Class<T> entityClass, String orderBy,
        boolean isAsc) {
        Assert.hasText(orderBy);

        if (isAsc) {
            return getHibernateTemplate()
                       .findByCriteria(DetachedCriteria.forClass(
                    entityClass).addOrder(Order.asc(orderBy)));
        } else {
            return getHibernateTemplate()
                       .findByCriteria(DetachedCriteria.forClass(
                    entityClass).addOrder(Order.desc(orderBy)));
        }
    }

    /**
     * 保存对象.
     */
    public void save(Object o) {
        getHibernateTemplate().saveOrUpdate(o);
    }

    /**
     * 删除对象.
     */
    public void remove(Object o) {
        getHibernateTemplate().delete(o);
    }

    /**
     * 根据ID删除对象.
     */
    public <T> void removeById(Class<T> entityClass, Serializable id) {
        remove(get(entityClass, id));
    }

    /**
     * 删除所有对象.
     */
    public <T> void removeAll(Collection<T> all) {
        getHibernateTemplate().deleteAll(all);
    }

    /** * 清空session. */
    public void flush() {
        getHibernateTemplate().flush();
    }

    public void clear() {
        getHibernateTemplate().clear();
    }

    /**
     * 创建Query对象. 对于需要first,max,fetchsize,cache,cacheRegion等诸多设置的函数,可以在返回Query后自行设置.
     * 留意可以连续设置,如下：
     * <pre>
     * dao.getQuery(hql).setMaxResult(100).setCacheable(true).list();
     * </pre>
     * 调用方式如下：
     * <pre>
     *        dao.createQuery(hql)
     *        dao.createQuery(hql,arg0);
     *        dao.createQuery(hql,arg0,arg1);
     *        dao.createQuery(hql,new Object[arg0,arg1,arg2])
     * </pre>
     *
     * @param values 可变参数.
     */
    public Query createQuery(String hql, Object... values) {
        Assert.hasText(hql);

        Query query = getSession().createQuery(hql);

        for (int i = 0; i < values.length; i++) {
            query.setParameter(i, values[i]);
        }

        return query;
    }

    /**
     * 创建Criteria对象.
     *
     * @param criterions 可变的Restrictions条件列表,见{@link #createQuery(String,Object...)}
     */
    public <T> Criteria createCriteria(Class<T> entityClass,
        Criterion... criterions) {
        Criteria criteria = getSession().createCriteria(entityClass);

        for (Criterion c : criterions) {
            criteria.add(c);
        }

        return criteria;
    }

    /**
     * 创建Criteria对象，带排序字段与升降序字段.
     *
     * @see #createCriteria(Class,Criterion[])
     */
    public <T> Criteria createCriteria(Class<T> entityClass,
        String orderBy, boolean isAsc, Criterion... criterions) {
        Assert.hasText(orderBy);

        Criteria criteria = createCriteria(entityClass, criterions);

        if (isAsc) {
            criteria.addOrder(Order.asc(orderBy));
        } else {
            criteria.addOrder(Order.desc(orderBy));
        }

        return criteria;
    }

    /**
     * 根据hql查询,直接使用HibernateTemplate的find函数.
     *
     * @param values 可变参数,见{@link #createQuery(String,Object...)}
     */
    public List find(String hql, Object... values) {
        Assert.hasText(hql);

        return getHibernateTemplate().find(hql, values);
    }

    /**
     * 根据属性名和属性值查询对象.
     *
     * @return 符合条件的对象列表
     */
    public <T> List<T> findBy(Class<T> entityClass, String propertyName,
        Object value) {
        Assert.hasText(propertyName);

        return createCriteria(entityClass,
            Restrictions.eq(propertyName, value)).list();
    }

    /**
     * 根据属性名和属性值查询对象,带排序参数.
     */
    public <T> List<T> findBy(Class<T> entityClass, String propertyName,
        Object value, String orderBy, boolean isAsc) {
        Assert.hasText(propertyName);
        Assert.hasText(orderBy);

        return createCriteria(entityClass, orderBy, isAsc,
            Restrictions.eq(propertyName, value)).list();
    }

    /**
     * 根据属性名和属性值查询唯一对象.
     *
     * @return 符合条件的唯一对象 or null if not found.
     */
    public <T> T findUniqueBy(Class<T> entityClass, String propertyName,
        Object value) {
        Assert.hasText(propertyName);

        return (T) createCriteria(entityClass,
            Restrictions.eq(propertyName, value)).uniqueResult();
    }

    /**
     * 分页查询函数，使用hql.
     *
     * @param pageNo 页号,从1开始.
     */
    public Page pagedQuery(String hql, int pageNo, int pageSize,
        Object... values) {
        Assert.hasText(hql);
        Assert.isTrue(pageNo >= 1, "pageNo should start from 1");

        // Count查询
        String countQueryString = " select count (*) "
            + removeSelect(removeOrders(hql));
        List countlist = getHibernateTemplate()
                             .find(countQueryString, values);
        long totalCount = (Long) countlist.get(0);

        if (totalCount < 1) {
            return new Page();
        }

        // 实际查询返回分页对象
        int startIndex = Page.getStartOfPage(pageNo, pageSize);
        Query query = createQuery(hql, values);
        List list = query.setFirstResult(startIndex).setMaxResults(pageSize)
                         .list();

        return new Page(startIndex, totalCount, pageSize, list);
    }

    /**
     * 分页查询函数，使用已设好查询条件与排序的<code>Criteria</code>.
     *
     * @param pageNo 页号,从1开始.
     * @return 含总记录数和当前页数据的Page对象.
     */
    public Page pagedQuery(Criteria criteria, int pageNo, int pageSize) {
        Assert.notNull(criteria);
        Assert.isTrue(pageNo >= 1, "pageNo should start from 1");

        CriteriaImpl impl = (CriteriaImpl) criteria;

        // 先把Projection和OrderBy条件取出来,清空两者来执行Count操作
        Projection projection = impl.getProjection();
        List<CriteriaImpl.OrderEntry> orderEntries;

        try {
            orderEntries = (List) BeanUtils.forceGetProperty(impl,
                    "orderEntries");
            BeanUtils.forceSetProperty(impl, "orderEntries",
                new ArrayList());
        } catch (Exception e) {
            throw new InternalError(
                " Runtime Exception impossibility throw ");
        }

        // 执行查询
        int totalCount = (Integer) criteria.setProjection(Projections
                .rowCount()).uniqueResult();

        // 将之前的Projection和OrderBy条件重新设回去
        criteria.setProjection(projection);

        if (projection == null) {
            criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
        }

        try {
            BeanUtils.forceSetProperty(impl, "orderEntries", orderEntries);
        } catch (Exception e) {
            throw new InternalError(
                " Runtime Exception impossibility throw ");
        }

        // 返回分页对象
        if (totalCount < 1) {
            return new Page();
        }

        int startIndex = Page.getStartOfPage(pageNo, pageSize);
        List list = criteria.setFirstResult(startIndex)
                            .setMaxResults(pageSize).list();

        return new Page(startIndex, totalCount, pageSize, list);
    }

    /**
     * 分页查询函数，根据entityClass和查询条件参数创建默认的<code>Criteria</code>.
     *
     * @param pageNo 页号,从1开始.
     * @return 含总记录数和当前页数据的Page对象.
     */
    public Page pagedQuery(Class entityClass, int pageNo, int pageSize,
        Criterion... criterions) {
        Criteria criteria = createCriteria(entityClass, criterions);

        return pagedQuery(criteria, pageNo, pageSize);
    }

    /**
     * 分页查询函数，根据entityClass和查询条件参数,排序参数创建默认的<code>Criteria</code>.
     *
     * @param pageNo 页号,从1开始.
     * @return 含总记录数和当前页数据的Page对象.
     */
    public Page pagedQuery(Class entityClass, int pageNo, int pageSize,
        String orderBy, boolean isAsc, Criterion... criterions) {
        Criteria criteria = createCriteria(entityClass, orderBy, isAsc,
                criterions);

        return pagedQuery(criteria, pageNo, pageSize);
    }

    /**
     * 判断对象某些属性的值在数据库中是否唯一.
     *
     * @param uniquePropertyNames 在POJO里不能重复的属性列表,以逗号分割 如"name,loginid,password"
     */
    public <T> boolean isUnique(Class<T> entityClass, Object entity,
        String uniquePropertyNames) {
        Assert.hasText(uniquePropertyNames);

        Criteria criteria = createCriteria(entityClass)
                                .setProjection(Projections.rowCount());
        String[] nameList = uniquePropertyNames.split(",");

        try {
            // 循环加入唯一列
            for (String name : nameList) {
                criteria.add(Restrictions.eq(name,
                        PropertyUtils.getProperty(entity, name)));
            }

            // 以下代码为了如果是update的情况,排除entity自身.
            String idName = getIdName(entityClass);

            // 取得entity的主键值
            Serializable id = getId(entityClass, entity);

            // 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
            if (id != null) {
                criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
            }
        } catch (Exception e) {
            ReflectionUtils.handleReflectionException(e);
        }

        return (Integer) criteria.uniqueResult() == 0;
    }

    /**
     * 取得对象的主键值,辅助函数.
     */
    public Serializable getId(Class entityClass, Object entity)
        throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException {
        Assert.notNull(entity);
        Assert.notNull(entityClass);

        return (Serializable) PropertyUtils.getProperty(entity,
            getIdName(entityClass));
    }

    /**
     * 取得对象的主键名,辅助函数.
     */
    public String getIdName(Class clazz) {
        Assert.notNull(clazz);

        ClassMetadata meta = getSessionFactory().getClassMetadata(clazz);
        Assert.notNull(meta,
            "Class " + clazz + " not define in hibernate session factory.");

        String idName = meta.getIdentifierPropertyName();
        Assert.hasText(idName,
            clazz.getSimpleName() + " has no identifier property define.");

        return idName;
    }

    /**
     * 去除hql的select 子句，未考虑union的情况,用于pagedQuery.
     *
     * @see #pagedQuery(String,int,int,Object[])
     */
    private static String removeSelect(String hql) {
        Assert.hasText(hql);

        int beginPos = hql.toLowerCase().indexOf("from");
        Assert.isTrue(beginPos != -1,
            " hql : " + hql + " must has a keyword 'from'");

        return hql.substring(beginPos);
    }

    /**
     * 去除hql的orderby 子句，用于pagedQuery.
     *
     * @see #pagedQuery(String,int,int,Object[])
     */
    private static String removeOrders(String hql) {
        Assert.hasText(hql);

        Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*",
                Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(hql);
        StringBuffer sb = new StringBuffer();

        while (m.find()) {
            m.appendReplacement(sb, "");
        }

        m.appendTail(sb);

        return sb.toString();
    }

    /**
     * 获得完成初始化的实体beans.
     *
     * @param clz 类型
     * @param id 主键
     * @return T 实体bean
     */
    public <T> T initialize(Class<T> clz, Serializable id) {
        T entity = get(clz, id);
        Hibernate.initialize(entity);

        return entity;
    }

    // count
    /**
     * 获得总数.
     */
    public <T> Long getCount(Class<T> clz) {
        Criteria c = getSession().createCriteria(clz)
                         .setProjection(Projections.rowCount());

        Object result = c.uniqueResult();

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }

    /**
     * 获得总数.
     */
    public <T> Long getCount(Class<T> clz, String name, Object value) {
        Criteria c = getSession().createCriteria(clz)
                         .add(Restrictions.eq(name, value))
                         .setProjection(Projections.rowCount());

        Object result = c.uniqueResult();

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }

    /**
     * 获得总数.
     */
    public Long getCount(String hql, Object... values) {
        Object result = getHibernateTemplate().find(hql, values).get(0);

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }

    // sum
    /**
     * 求和.
     */
    public <T> Long getSum(Class<T> clz, String fieldName) {
        Criteria c = getSession().createCriteria(clz)
                         .setProjection(Projections.sum(fieldName));

        Object result = c.uniqueResult();

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }

    /**
     * 求和.
     */
    public <T> Long getSum(Class<T> clz, String fieldName, String name,
        Object value) {
        Criteria c = getSession().createCriteria(clz)
                         .setProjection(Projections.sum(fieldName))
                         .add(Restrictions.eq(name, value));

        Object result = c.uniqueResult();

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }

    /**
     * 求和.
     */
    public Long getSum(String hql, Object... values) {
        Object result = getHibernateTemplate().find(hql, values).get(0);

        if (result == null) {
            return 0L;
        } else if (result instanceof Integer) {
            return ((Integer) result).longValue();
        } else if (result instanceof Long) {
            return (Long) result;
        } else {
            return 0L;
        }
    }
}
